<template>
  <div style="flex:1;display: flex">
    <!-- Sidebar -->
    <div class="sidebar border-end py-xl-4 py-3 px-xl-4 px-3" :style="tabWidth">
      <div class="tab-content">
        <!-- Chat Records -->
        <div class="tab-pane fade active show" id="nav-tab-chat" role="tabpanel" v-if="showTab === 'nav-tab-chat'">
          <div class="d-flex justify-content-between align-items-center mb-4">
            <h3 class="mb-0 text-primary">ChatGPT</h3>
            <div>
              <button class="btn btn-dark" type="button" @click="handleAdd">新建会话</button>
            </div>
          </div>
          <ul class="chat-list">
            <li class="header d-flex justify-content-between ps-3 pe-3 mb-1">
              <span>RECENT CHATS</span>
            </li>
            <li v-for="(item, index) in conversationList" :class="[item.active ? 'active' : '']"
                @click="handleSwitch(item.uuid)">
              <div class="hover_action" v-if="!item.isEdit">
                <button type="button" class="btn btn-link text-info" @click="handleEdit(item.uuid)">
                  <i class="zmdi zmdi-edit font-22"></i>
                </button>
                <button type="button" class="btn btn-link text-danger" @click="handleDele(item.uuid)">
                  <i class="zmdi zmdi-delete font-22"></i>
                </button>
              </div>
              <a href="#" class="card">
                <div class="card-body">
                  <div class="media">
                    <div class="avatar me-3">
                      <span class="status rounded-circle"></span>
                      <img class="avatar rounded-circle"
                           :style="[item.active ? 'filter:grayscale(0)' : 'filter:grayscale(1)']"
                           src="../assets/chatgpt.jpg" alt="avatar"></div>
                    <div class="media-body overflow-hidden">
                      <div class="d-flex align-items-center mb-1">
                        <h6 class="text-truncate mb-0 me-auto">
                          <span v-if="!item.isEdit">{{ item.title }}</span>
                          <div v-else style="display: flex;align-items: center">
                            <input v-model="edit_title">
                            <i @click.stop="handleSaveEdit(item.uuid)" class="zmdi zmdi-save font-22"
                               style="margin-left: 5px"></i>
                          </div>
                        </h6>
                        <p v-if="!item.isEdit" class="small text-muted text-nowrap ms-4 mb-0">{{ item.createDate }}</p>
                      </div>
                      <div class="text-truncate">{{ item.lastChatContent }}</div>
                    </div>
                  </div>
                </div>
              </a>
            </li>
          </ul>
        </div>
        <!-- end Chat Records -->
      </div>
    </div>

    <div class="main px-xl-5 px-lg-4 px-3">
      <div class="chat-body">
        <!-- Chat Box Header -->
        <div class="chat-header border-bottom py-xl-4 py-md-3 py-2">
          <div class="container-xxl">
            <div class="row align-items-center">
              <div class="col-6 col-xl-4">
                <div class="media">
                  <div class="me-3 show-user-detail">
                    <span class="status rounded-circle"></span>
                    <img class="avatar rounded-circle" src="../assets/chatgpt.jpg" alt="avatar"></div>
                  <div class="media-body overflow-hidden">
                    <div class="d-flex align-items-center mb-1">
                      <h6 class="text-truncate mb-0 me-auto">ChatGPT 3.5</h6></div>
                    <div class="text-truncate">Powered By OpenAI</div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
        <!-- end Chat Box Header -->

        <div class="chat-content" id="scrollRef" ref="scrollRef">
          <div class="container-xxl">
            <ul class="list-unstyled py-4" v-for="(item, index) of messageList">
              <!-- Right Message -->
              <li class="d-flex message right">
                <div class="message-body">
                  <span class="date-time text-muted">{{ formatDate(item.send.created, 'yyyy-MM-dd hh:mm:ss') }}</span>
                  <div class="message-row d-flex align-items-center justify-content-end">
                    <div class="message-content border">
                      <div class="p-3" v-highlight v-html="marked(item.send.messages[0].content)" ></div>
                      <div class="attachment" v-show="item.send.messages[0].fileName" @click="handleBackDoc">
                        <div class="media mt-2">
                          <div class="avatar me-2">
                            <div class="avatar rounded no-image orange">
                              <i class="zmdi zmdi-collection-pdf"></i>
                            </div>
                          </div>
                          <div class="media-body overflow-hidden">
                            <h6 class="text-truncate mb-0">{{ item.send.messages[0].fileName }}</h6>
                            <span class="file-size">{{ item.send.messages[0].fileSize }}</span>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                <div class="mr-lg-3 mx-2">
                  <img class="avatar sm rounded-circle" src="../assets/user.png" alt="avatar">
                </div>
              </li>
              <!-- end Right Message -->
              <!-- Left Message -->
              <li class="d-flex message" v-if="item.receive">
                <div class="mr-lg-3 me-2">
                  <img class="avatar sm rounded-circle" src="../assets/chatgpt.jpg" alt="avatar"></div>
                <div class="message-body">
                  <span class="date-time text-muted">
                    {{formatDate(item.receive.created * 1000, 'yyyy-MM-dd hh:mm:ss')}}
                  </span>
                  <div class="message-row d-flex align-items-center">
                    <div class="message-content">
                      <div class="p-3" v-highlight v-html="marked(item.receive.choices[0].message?item.receive.choices[0].message.content:item.receive.choices[0].delta.content)" ></div>
                    </div>
                  </div>
                </div>
              </li>
              <li class="d-flex message" v-if="item.loading">
                <div class="mr-lg-3 me-2">
                  <img class="avatar sm rounded-circle" src="../assets/chatgpt.jpg" alt="avatar"></div>
                <div class="message-body">
                  <div class="message-row d-flex align-items-center">
                    <div class="message-content p-2">
                      <div class="wave">
                        <span class="dot"></span>
                        <span class="dot"></span>
                        <span class="dot"></span>
                      </div>
                    </div>
                  </div>
                </div>
              </li>
              <!-- end Loading Message -->
            </ul>
          </div>
        </div>
        <!-- Message Input Box -->
        <div class="chat-footer py-xl-4 py-lg-2 py-2">
          <div class="container-xxl">
            <div class="row">
              <div class="col-12">
                <form @submit.prevent="handleSubmit">
                  <div class="input-group d-flex justify-content-between">
                    <el-popover title="切换模型" :visible="modelDataShow" placement="top-start" :width="600"
                                trigger="click">
                      <template #reference>
                        <el-button @click="modelDataShow=true;">{{ currentGPT.name }}</el-button>
                      </template>
                      <el-table highlight-current-row :data="modelData.tableData" :show-header="false"
                                @current-change="handleCurrentChange">
                        <el-table-column width="180" prop="name"/>
                        <el-table-column width="100" prop="price"/>
                        <el-table-column prop="description"/>
                      </el-table>
                    </el-popover>
                    <div>
                      <el-button link size="small" @click="downloadMessage"><i
                          class="zmdi zmdi-cloud-download font-22"></i></el-button>
                      <el-button @click="enableContextSwitch" link size="small"
                                 :type="enableContext?'success':'danger'">
                        <i class="zmdi zmdi-time font-22"></i>
                      </el-button>
                      <el-button @click="clearChat" link size="small" type="danger">
                        <i class="zmdi zmdi-delete font-22"></i>
                      </el-button>
                    </div>
                  </div>
                  <div class="input-group">
                    <el-input
                        :disabled="buttonDisabled"
                        @keyup.enter="handleKeyDown"
                        v-model="prompt"
                        placeholder="Please input"
                        class="form-control border-0 pl-0"
                        rows="5"
                        type="textarea"
                    ></el-input>
                    <div class="input-group-append" style="position:absolute;bottom:10px;right: 5px">
                      <div class="input-group-text border-0 pr-0">
                        <el-button native-type="submit" class="btn btn-primary" :disabled="buttonDisabled||!prompt"
                                @click="handleSubmit">
                          <i class="zmdi zmdi-mail-send"></i>
                          发送
                        </el-button>
                      </div>
                    </div>
                  </div>
                </form>
              </div>
            </div>
          </div>
        </div>
        <!-- end Message Input Box -->
      </div>
    </div>
  </div>
</template>
<script setup lang='ts'>
import {reactive, ref} from 'vue'
import {useRoute} from 'vue-router'
import {router} from '@/router'
import {useScroll} from './hooks/useScroll'
import "vue3-pdf-app/dist/icons/main.css"
import {encode} from 'gpt-tokenizer'
import {ElMessage, ElMessageBox} from "element-plus";
import {formatDate} from "@/utils/DateTime";
import { marked }from 'marked';


const {scrollRef, scrollToBottom} = useScroll()

// Conversation and PDF preview panel toggle control
let showTab = ref<string>("nav-tab-chat")
let tabWidth = ref<string>("")

const config = ref<{}>({
  sidebar: true,
  toolbar: {
    toolbarViewerLeft: {
      findbar: true,
      previous: true,
      next: true,
      pageNumber: false,
    },
    toolbarViewerRight: {
      presentationMode: true,
      openFile: false,
      print: false,
      download: false,
      viewBookmark: false,
    },
    toolbarViewerMiddle: {
      zoomOut: true,
      zoomIn: true,
      scaleSelectContainer: true,
    }
  },
})

interface GPTModel {
  date: string
  name: string
  address: string
}

const modelData = reactive({
  tableData: [
    {
      name: "gpt-3.5-turbo",
      price: "免费",
      description: "说明",
    },
    {
      name: "gpt-4",
      price: "免费",
      description: "说明",
    },
    {
      name: "gpt-4o",
      price: "免费",
      description: "说明",
    },
    {
      name: "gpt-3.5-turbo-1106",
      price: "免费",
      description: "说明",
    },
    {
      name: "文明一言",
      price: "免费",
      description: "说明",
    },
    {
      name: "文明一言4.0",
      price: "免费",
      description: "说明",
    }
  ]
})
const modelDataShow = ref(false)
const currentGPT = ref(<any>{
  name: "gpt-3.5-turbo",
  price: "免费",
  description: "说明",
})
const handleCurrentChange = (val: GPTModel) => {
  currentGPT.value = val;
  modelDataShow.value = false;
}
const enableContext = ref(true);
// Message input box
const prompt = ref<string>('')

// Loading state and button state
const buttonDisabled = ref<boolean>(false)

// Get uuid from URL params
const route = useRoute()
let {uuid} = route.params as { uuid: string }
const edit_title = ref("");

interface Conversation {
  title: string;
  uuid: string;
  isEdit: boolean;
  createDate: string;
  lastChatContent: string;
  active: boolean;
}

interface Message {
  send: {
    model: string;
    messages: {
      role: string;
      content: string;
      fileName: any;
      fileSize: number;
    }[];
    temperature: number;
  };
  loading: boolean;
  receive?: {
    model: string;
    choices: {
      message?: {
        content: string;
      };
      delta: {
        content: string;
      };
    }[];
  };
}

// Conversation list and message list
var conversationList = ref<Conversation[]>([])
var messageList = ref<Message[]>([]);

let conversations = window.localStorage.getItem("chatStore")
if (conversations) {
  conversationList.value = JSON.parse(conversations)
}

// Check if new conversation
if (!uuid || uuid === '0') {
  uuid = Date.now().toString()

  // Initialize empty conversation
  if (!conversations) {
    conversationList.value.push({
      title: 'New Chat',
      uuid: uuid,
      isEdit: false,
      createDate: new Date().toLocaleString(),
      lastChatContent: 'Hello I am ChatGPT3.5...',
      active: true
    })
  } else {
    // If has history, get last conversation
    let lastConversation = conversationList.value[conversationList.value.length - 1]
    uuid = lastConversation.uuid

    let messages = window.localStorage.getItem(uuid)
    if (messages) {
      messageList.value = JSON.parse(messages)
    }

    router.push({name: 'chat', params: {uuid}})
  }
} else {
  // Load current conversation messages
  let messages = window.localStorage.getItem(uuid)
  if (messages) {
    messageList.value = JSON.parse(messages)
  }

  conversationList.value.forEach((item, index) => {
    if (item.uuid == uuid) {
      item.active = true
    } else {
      item.active = false
    }
  })

  scrollToBottom()
}

const downloadMessage = () => {
  ElMessageBox.confirm('是否将聊天记录保存为图片?', '保存聊天记录到图片', {
    type: 'warning',
    confirmButtonText: '是',
    cancelButtonText: '否',
    showClose: false
  }).then((res) => {
    if (res == "confirm") {

    }
  }).catch(() => {
  });
}
const enableContextSwitch = () => {
  enableContext.value = !enableContext.value
  if (enableContext.value)
    ElMessage.success("当前模式下, 发送消息会携带之前的聊天记录")
  else
    ElMessage.warning("当前模式下, 发送消息不会携带之前的聊天记录")
}
const clearChat = () => {
  ElMessageBox.confirm('是否清空聊天内容?', '清空会话', {
    type: 'warning',
    confirmButtonText: '是',
    cancelButtonText: '否',
    showClose: false
  }).then((res) => {
    if (res == "confirm") {

    }
  }).catch(() => {
  });
}

// Set active conversation
function handleAdd() {
  // Reset the message record of the new conversation
  messageList.value = []

  // Reset the active status of the conversation list
  conversationList.value.forEach((item, index) => {
    item.active = false
  })

  // Initialize an empty conversation
  uuid = Date.now().toString()

  conversationList.value.unshift({
    title: "New Chat",
    uuid: uuid,
    isEdit: false,
    createDate: new Date().toLocaleString(),
    lastChatContent: 'Hello I am ChatGPT3.5...',
    active: true
  })

  // Save the conversation to local storage
  window.localStorage.setItem("chatStore", JSON.stringify(conversationList.value))
}


// Switch conversation
function handleSwitch(selectedUuid: string) {
  uuid = selectedUuid

  // Reset message record of the new conversation
  let messages = window.localStorage.getItem(selectedUuid)
  if (messages) {
    messageList.value = JSON.parse(messages)
  } else {
    messageList.value = []
  }

  // Reset active status of the conversation list
  conversationList.value.forEach((item, index) => {
    if (item.uuid == selectedUuid) {
      item.active = true
    } else {
      item.active = false
    }
  })

  router.push({name: 'chat', params: {uuid}})
}

// File upload related
var fileName = ref()
var fileSize = ref<number>(0)
var formattedFileSize = ref<string>('0B')
var fileUploadCard = ref<boolean>(false)

var fileContent = ref()


// Handle file upload
function handleUpload(e: Event) {
  const target = e.target as HTMLInputElement;
  if (target.files && target.files[0].size >= 5 * 1024 * 1024) {
    alert('Maximum file size limit is 5MB')
    return
  } else if (!target.files || target.files.length === 0) {
    alert('Please select a file')
    return
  }

  // Set file upload style
  fileName.value = target.files[0].name
  fileSize.value = target.files[0].size
  formatFileSize()

  // Preview PDF
  showTab.value = 'nav-tab-doc'
  tabWidth.value = 'width: 60%'


  // Upload file and extract content
  const formData = new FormData()
  formData.append('doc', target.files[0])

  fetch(import.meta.env.VITE_API_UPLOAD, {
    method: 'POST',
    body: formData,
  })
      .then(response => response.text())
      .catch(error => console.error('Error:', error))
      .then(function (docContent) {
        if (typeof docContent !== 'string') {
          alert("Failed to extract file content")
          return
        }

        const tokens = encode(docContent)

        if (tokens.length > 4096) {
          alert("Exceeded maximum token limit of 4096")
          fileName.value = ''
          fileSize.value = 0
          formattedFileSize.value = '0B'
        } else {
          // Set the extracted content
          fileContent.value = docContent

          // Show file upload card
          fileUploadCard.value = true
        }
      })
}

function handleBackChat() {
  showTab.value = 'nav-tab-chat'
  tabWidth.value = ''
}

function handleBackDoc() {
  showTab.value = 'nav-tab-doc'
  tabWidth.value = 'width: 40%'
}

// Format file size in Bytes, KB, MB, GB
function formatFileSize() {
  if (fileSize.value < 1024) {
    formattedFileSize.value = fileSize.value + 'B';
  } else if (fileSize.value < (1024 * 1024)) {
    var temp = fileSize.value / 1024
    formattedFileSize.value = temp.toFixed(2) + 'KB'
  } else if (fileSize.value < (1024 * 1024 * 1024)) {
    var temp = fileSize.value / (1024 * 1024)
    formattedFileSize.value = temp.toFixed(2) + 'MB'
  } else {
    var temp = fileSize.value / (1024 * 1024 * 1024);
    formattedFileSize.value = temp.toFixed(2) + 'GB'
  }
}

// Submit message
function handleSubmit() {
  onConversation()
}

// Stream request to ChatGPT3.5
async function onConversation() {
  let message = prompt.value
  if (!message || message.trim() === '')
    return

  // Clear input box and disable button
  prompt.value = ''
  buttonDisabled.value = true
  fileUploadCard.value = false

  // Send message (for local display, not directly sent to GPT)
  messageList.value.push({
    send: {
      model: currentGPT.value.name,
      created: Date.now(),
      messages: [
        {
          role: "user",
          content: message,
          fileName: fileName.value,
          fileSize: fileSize.value,
        },
      ],
      temperature: 0.7,
    },
    loading: true,
  });

  scrollToBottom()

  // Stream request to ChatGPT3.5
  try {
    if (fileContent.value) {
      message += ', Uploaded file content: ' + fileContent.value
    }

    let data = {
      "model": currentGPT.value.name,
      "messages": [{"role": "user", "content": message}],
      "temperature": 0.7,
      "stream": true
    }

    let headers = {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + import.meta.env.VITE_API_KEY,
    }

    // Send request
    let response = await fetch(import.meta.env.VITE_APP_URL, {
      method: 'POST',
      headers: headers,
      body: JSON.stringify(data)
    })

    // Reset file upload related states immediately after sending to ChatGPT
    fileName.value = ''
    fileSize.value = 0
    formattedFileSize.value = '0B'

    if (!response.ok) {
      throw new Error('Network response was not ok')
    }

    // Read the data returned from the stream
    const reader = response.body?.getReader();

    const textDecoder = new TextDecoder()
    let result = true
    while (reader && result) {
      // Get a chunk
      const {done, value} = await reader.read()

      if (done) {
        console.log('Stream ended')
        result = false

        // Restore button state
        buttonDisabled.value = false
        fileContent.value = ''

        // Save current messages
        window.localStorage.setItem(uuid, JSON.stringify(messageList.value))
        window.localStorage.setItem("chatStore", JSON.stringify(conversationList.value))
        break
      }

      // Convert chunk string to array
      let chunkText = textDecoder.decode(value)
      chunkText = chunkText.replace(/data:/g, '')
      let results = chunkText.split('\n\n').filter(Boolean)

      // Iterate through the array and process multiple chunks
      for (let i = 0; i < results.length; i++) {
        var chunk = results[i]
        if (chunk.indexOf('DONE') == -1) {
          var chunkData = JSON.parse(chunk)
          if (chunkData.choices[0].delta.content) {
            if (!messageList.value[messageList.value.length - 1].receive) {
              // If it is the first result, set the state directly
              messageList.value[messageList.value.length - 1].receive = chunkData
              messageList.value[messageList.value.length - 1].loading = false
            } else {
              const lastMessage = messageList.value[messageList.value.length - 1]?.receive;
              if (lastMessage && lastMessage.choices[0].delta.content) {
                lastMessage.choices[0].delta.content += chunkData.choices[0].delta.content;
              }
            }
            scrollToBottom()
          }
        }
      }
    }
  } catch (e) {
    console.log(e)
  }
}

function handleEdit(selectedUuid: string) {
  conversationList.value.forEach((item, index) => {
    item.isEdit = item.uuid == selectedUuid;
    edit_title.value = item.title;
  })
}

function handleSaveEdit(selectedUuid: string) {
  conversationList.value.forEach((item, index) => {
    item.isEdit = false;
    if (item.uuid == selectedUuid) item.title = edit_title.value;
  })
}


async function handleDele(selectedUuid: string) {
  const res = await ElMessageBox.confirm('是否清空聊天内容?', '清空会话', {
    type: 'warning',
    confirmButtonText: '是',
    cancelButtonText: '否',
    showClose: false
  })
  if (res !== "confirm") return
  conversationList.value.forEach((item, index) => {
    if (item.uuid == selectedUuid) {
      conversationList.value.splice(index, 1)

      // Save the conversation to local storage
      window.localStorage.setItem("chatStore", JSON.stringify(conversationList.value))
      return false
    }
  })

  // Reset the message records of the new conversation
  if (uuid == selectedUuid) {
    let messages = window.localStorage.getItem(selectedUuid)
    if (messages) {
      window.localStorage.removeItem(selectedUuid)
      messageList.value = []
    }
  }
}

const handleKeyDown = (event) => {
  console.log(event);
  if (event.ctrlKey && event.keyCode === 13) {
    prompt.value = prompt.value + "\n"
  } else if (event.keyCode === 13) {
    handleSubmit();
  }
  event.stopPropagation();
}
</script>
<style scoped>
.pl-0 {
  padding-left: 0 !important;
  padding-right: 0 !important;
}
:deep(.message-content p){
  margin: 0;
}
</style>